﻿using System;
using System.Collections.Generic;
using System.Windows;

namespace MapEngine
{
    public delegate float? GetHeightDelegate(Point position);
    public enum TileType { Square, Hexagonal };

    public class TileBuilder {
       
        public GetHeightDelegate GetHeight;
        public Point TileSize;

        public TileBuilder(Point tileSize, GetHeightDelegate getHeight)
        {
            TileSize = tileSize;
            GetHeight = getHeight;
        }

        public Tile CreateTile(Point position, TileType type)
        {
            return new Tile(position, TileSize, GetHeight, type);
        }
    }

    public class Tile
    {
        //private List<Point3d> Points = new List<Point3d>();
        private GetHeightDelegate GetHeight;
        private Point TileSize;
        public List<List<Point3d>> ShapeVertices;
        public Point MapPosition; // 0,0; 1,0
        public float Height
        {
            get {
                return innerHeight.Height;
                //return (Points[0].Height + Points[1].Height + Points[2].Height + Points[3].Height) / 4; 
            }
        }
        /*
        public event MouseButtonEventHandler MouseLeftButtonUp;
        public event MouseEventHandler MouseMove;
        public event MouseEventHandler MouseLeave;
        public event MouseEventHandler MouseEnter;
        */
        public Tile(Point position, Point tileSize, GetHeightDelegate getHeight,TileType type)
        {
            MapPosition = position;
            GetHeight = getHeight;
            TileSize = tileSize;
            innerHeight = new Point3d(
                new Point(MapPosition.X*TileSize.X,MapPosition.Y*TileSize.Y), 
                GetHeight(MapPosition).Value);
            CreateVertices(type);
        }

        enum tiles { self, south, north, west, east, southeast, southwest, nordeste, norwest }

        Point3d innerHeight;
            
        IDictionary<tiles, Point3d> DiagonalHeights()
        {

            IDictionary<tiles, Point3d> vertices = new Dictionary<tiles, Point3d>();

            Point seTile = MapPosition; seTile.Y--; seTile.X++;
            Point swTile = MapPosition; swTile.Y--; swTile.X--;
            Point neTile = MapPosition; neTile.Y++; neTile.X++;
            Point nwTile = MapPosition; nwTile.Y++; nwTile.X--;

            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.southeast,
                new Point3d(
                    new Point(seTile.X*TileSize.X,seTile.Y*TileSize.Y), 
                    GetHeight(seTile).GetValueOrDefault(float.NaN))));// seHeight = seHeight.HasValue ? seHeight : sHeight.HasValue ? sHeight : eHeight;
            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.southwest,
                new Point3d(
                    new Point(swTile.X * TileSize.X, swTile.Y * TileSize.Y),
                    GetHeight(swTile).GetValueOrDefault(float.NaN))));// swHeight = swHeight.HasValue ? swHeight : sHeight.HasValue ? sHeight : wHeight;
            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.nordeste,
                new Point3d(
                    new Point(neTile.X * TileSize.X, neTile.Y * TileSize.Y),
                    GetHeight(neTile).GetValueOrDefault(float.NaN))));// neHeight = neHeight.HasValue ? neHeight : nHeight.HasValue ? nHeight : eHeight;
            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.norwest,
                new Point3d(
                    new Point(nwTile.X * TileSize.X, nwTile.Y * TileSize.Y),
                    GetHeight(nwTile).GetValueOrDefault(float.NaN))));// nwHeight = nwHeight.HasValue ? nwHeight : nHeight.HasValue ? nHeight : wHeight;

            return vertices;
        }

        IDictionary<tiles,Point3d> NeighbourHeights() {

            IDictionary<tiles, Point3d> vertices = new Dictionary<tiles, Point3d>();

            Point sTile = MapPosition; sTile.Y--;
            Point nTile = MapPosition; nTile.Y++;
            Point wTile = MapPosition; wTile.X--;
            Point eTile = MapPosition; eTile.X++;

            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.south, 
                new Point3d(
                    new Point(sTile.X*TileSize.X,sTile.Y*TileSize.Y), 
                    GetHeight(sTile).GetValueOrDefault(float.NaN))));// sHeight = sHeight.HasValue ? sHeight : height;
            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.north,
                new Point3d(
                    new Point(nTile.X * TileSize.X, nTile.Y * TileSize.Y), 
                    GetHeight(nTile).GetValueOrDefault(float.NaN))));// nHeight = nHeight.HasValue ? nHeight : height;
            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.east,
                new Point3d(
                    new Point(eTile.X * TileSize.X, eTile.Y * TileSize.Y), 
                    GetHeight(eTile).GetValueOrDefault(float.NaN))));// eHeight = eHeight.HasValue ? eHeight : height;
            vertices.Add(new KeyValuePair<tiles, Point3d>(tiles.west,
                new Point3d(
                    new Point(wTile.X * TileSize.X, wTile.Y * TileSize.Y), 
                    GetHeight(wTile).GetValueOrDefault(float.NaN))));// wHeight = wHeight.HasValue ? wHeight : height;

            return vertices;
        }

        void CreateVertices(TileType type)
        {
            ShapeVertices = new List<List<Point3d>>();
            switch (type)
            {
                case TileType.Square:
                    ShapeVertices.Add(BuildSquare());
                    break;
                case TileType.Hexagonal:
                    ShapeVertices.Add(BuildHexagon());
                    break;
            }
        }

        List<Point3d> BuildSquare() {
            List<Point3d> list = new List<Point3d>();
            Point swCorner = new Point((MapPosition.X) * TileSize.X - TileSize.X / 2, (MapPosition.Y) * TileSize.Y - TileSize.Y / 2);
            Point seCorner = new Point((MapPosition.X) * TileSize.X + TileSize.X / 2, (MapPosition.Y) * TileSize.Y - TileSize.Y / 2);
            Point nwCorner = new Point((MapPosition.X) * TileSize.X - TileSize.X / 2, (MapPosition.Y) * TileSize.Y + TileSize.Y / 2);
            Point neCorner = new Point((MapPosition.X) * TileSize.X + TileSize.X / 2, (MapPosition.Y) * TileSize.Y + TileSize.Y / 2);

            IDictionary<tiles, Point3d> diagonalHeights = DiagonalHeights();
            IDictionary<tiles, Point3d> neighbourHeights = NeighbourHeights();
            
            list.Add(new Point3d(swCorner, 
                (
                innerHeight.Height + 
                (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height : innerHeight.Height ) +
                (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.southwest].Height) ? diagonalHeights[tiles.southwest].Height : 
                    (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height : 
                        (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height : 
                            innerHeight.Height)
                )))/4));

            list.Add(new Point3d(seCorner,
                (
                innerHeight.Height +
                (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.southeast].Height) ? diagonalHeights[tiles.southeast].Height :
                    (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height :
                        (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height :
                            innerHeight.Height)
                ))) / 4));

            list.Add(new Point3d(neCorner,
                (
                innerHeight.Height +
                (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.nordeste].Height) ? diagonalHeights[tiles.nordeste].Height :
                    (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height :
                        (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height :
                            innerHeight.Height)
                ))) / 4));

            list.Add(new Point3d(nwCorner,
                (
                innerHeight.Height +
                (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.norwest].Height) ? diagonalHeights[tiles.norwest].Height :
                    (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height : 
                        (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height :
                            innerHeight.Height)
                ))) / 4));
            
            return list;
        }

        List<Point3d> BuildHexagon() {
            List<Point3d> list = new List<Point3d>();

            float height = (float)TileSize.X/2;
            float side = (float) (height * 2 / Math.Sqrt(3));

            Point center = new Point(MapPosition.X*2*height+height*MapPosition.Y,MapPosition.Y*1.5f*side);

            Point sCorner = new Point(center.X, center.Y - side);
            Point seCorner = new Point(center.X+height, center.Y - side/2);
            Point swCorner = new Point(center.X-height, center.Y - side / 2);
            
            Point nCorner = new Point(center.X, center.Y + side);
            Point neCorner = new Point(center.X + height, center.Y + side / 2);
            Point nwCorner = new Point(center.X - height, center.Y + side / 2);
            
            IDictionary<tiles, Point3d> diagonalHeights = DiagonalHeights();
            IDictionary<tiles, Point3d> neighbourHeights = NeighbourHeights();

            list.Add(new Point3d(sCorner,innerHeight.Height));
            list.Add(new Point3d(sCorner,
                (
                innerHeight.Height/* +
                (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.southwest].Height) ? diagonalHeights[tiles.southwest].Height :
                    (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height :
                        (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height :
                            innerHeight.Height)
                ))) / 4*/)));

            list.Add(new Point3d(seCorner,
                (
                innerHeight.Height/* +
                (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.southeast].Height) ? diagonalHeights[tiles.southeast].Height :
                    (!float.IsNaN(neighbourHeights[tiles.south].Height) ? neighbourHeights[tiles.south].Height :
                        (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height :
                            innerHeight.Height)
                ))) / 4*/)));

            list.Add(new Point3d(neCorner,
                (
                innerHeight.Height /*+
                (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.nordeste].Height) ? diagonalHeights[tiles.nordeste].Height :
                    (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height :
                        (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height :
                            innerHeight.Height)
                ))) / 4*/)));

            list.Add(new Point3d(nCorner,
                (
                innerHeight.Height /*+
                (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.nordeste].Height) ? diagonalHeights[tiles.nordeste].Height :
                    (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height :
                        (!float.IsNaN(neighbourHeights[tiles.east].Height) ? neighbourHeights[tiles.east].Height :
                            innerHeight.Height)
                ))) / 4*/
                         )));

            list.Add(new Point3d(nwCorner,
                (
                innerHeight.Height /*+
                (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.norwest].Height) ? diagonalHeights[tiles.norwest].Height :
                    (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height :
                        (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height :
                            innerHeight.Height)
                ))) / 4*/)));

            list.Add(new Point3d(swCorner,
                (
                innerHeight.Height /*+
                (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height : innerHeight.Height) +
                (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height : innerHeight.Height) +
                (!float.IsNaN(diagonalHeights[tiles.norwest].Height) ? diagonalHeights[tiles.norwest].Height :
                    (!float.IsNaN(neighbourHeights[tiles.north].Height) ? neighbourHeights[tiles.north].Height :
                        (!float.IsNaN(neighbourHeights[tiles.west].Height) ? neighbourHeights[tiles.west].Height :
                            innerHeight.Height)
                ))) / 4*/
                         )));

            return list;
        }
    }
}